package com.text.jwt.utils;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import java.util.Calendar;
import java.util.Date;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Component
public class JwtUtils {

    @Value("${spring.security.jwt.key}")
    String key;

    @Value("${spring.security.jwt.expireDate}")
    int expireDate;

    @Resource
    StringRedisTemplate stringRedisTemplate;

    private Date expireTime() {
        Calendar calendar = Calendar.getInstance();
        // 设置过期时间
        calendar.add(Calendar.HOUR, expireDate);
        return calendar.getTime();
    }

    public String createJwt(UserDetails details, int id, String username) {
        Algorithm algorithm = Algorithm.HMAC256(key);
        Date expireTime = this.expireTime();
        return JWT.create()
                // 给每个令牌生成一个随机的UUID,作为它们的唯一标识
                .withJWTId(UUID.randomUUID().toString())
                .withClaim("id", id)
                .withClaim("username", username)
                .withClaim("authorities", details.getAuthorities().stream().map(GrantedAuthority::getAuthority).toList())
                .withExpiresAt(expireTime)
                .withIssuedAt(new Date())
                .sign(algorithm);

    }

    // 判断 token 是否合法
    public String converToken(String headerToken) {
        if (headerToken == null || !headerToken.startsWith("Bearer ")) return null;
        return headerToken.substring(7);
    }

    // 解析 jwt 令牌
    public DecodedJWT analysisJwt(String headerToken) {
        String token = this.converToken(headerToken);
        if (token == null) return null;
        Algorithm algorithm = Algorithm.HMAC256(key);
        // 验证 JWT 是否合法
        JWTVerifier jwtVerifier = JWT.require(algorithm).build();
        try {
            DecodedJWT verify = jwtVerifier.verify(token);
            if (this.isInvalidToken(verify.getId())) return null;
            Date expiresAt = verify.getExpiresAt();
            return new Date().after(expiresAt) ? null : verify;
        } catch (JWTVerificationException e) {
            return null;
        }
    }

    public UserDetails toUser(DecodedJWT jwt) {
        Map<String, Claim> claims = jwt.getClaims();
        return User
                .withUsername(claims.get("username").asString())
                .password("1111")
                .authorities(claims.get("authorities").asArray(String.class))
                .build();
    }

    // 让令牌失效的方法(退出登录的时候使用)
    public boolean invalidateJwt(String headerToken) {
        /*
        实现思路: 退出登录的时候,把jwt令牌存入redis里面,这样redis里面就是过期的jwt令牌
        只要发现有相同的令牌,就不允许请求. 这种方式叫做 黑名单
         */
        // 还是先从请求头中拿到jwt令牌
        String token = this.converToken(headerToken);
        if (token == null) return false;
        Algorithm algorithm = Algorithm.HMAC256(key);
        JWTVerifier jwtVerifier = JWT.require(algorithm).build();
        try {
            DecodedJWT jwt = jwtVerifier.verify(token);
            String jwtId = jwt.getId();
            return deleteToken(jwtId, jwt.getExpiresAt());
        } catch (JWTVerificationException e) {
            return false;
        }
    }

    private boolean deleteToken(String uuid, Date time) {
        if (this.isInvalidToken(uuid)) return false;
        Date now = new Date();
        /*
        传过来 jwt 的过期时间与现在时间进行比较
        long expire = time.getTime()- now.getTime();
        由于 jwt 里面的时间可能已经过期,导致减完的时间会变成负数,所以我们稍微处理一下
        如果为负数,那就把它变成 0
         */
        long expire = Math.max(time.getTime() - now.getTime(), 0);
        // TimeUnit.MILLISECONDS 代表毫秒
        stringRedisTemplate.opsForValue().set(Const.JWT_BLACK_LIST + uuid, "", expire, TimeUnit.MILLISECONDS);
        return true;
    }

    private boolean isInvalidToken(String uuid) {
        // 判断当前是否实效
        return Boolean.TRUE.equals(stringRedisTemplate.hasKey(Const.JWT_BLACK_LIST + uuid));
    }
}
